import sys, os, time, struct, random
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))

from threading import Thread, Lock
from bitbrook.leech_worker import LeechWorker
#from bitbrook.utilities.limited_socket import LSocket
import socket

COMMAND_FORMAT = "!16sI"

class RarestChunkStrategy(Thread):
    def __init__(self, brook, network):
        super(RarestChunkStrategy, self).__init__()
        self._brook = brook
        self._network = network
        #needed for thread stopping
        self._shutdown = False
        self._shutdown_signal = Lock()
        self._currently_leeching = set()
        
        self.done = False
        
    def run(self):
        with self._shutdown_signal:
            leechers = []
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            leechers.append(None)
            while not self._shutdown and not self.done:
                leecher_idx = 0
                for leecher in leechers:
                    #if leecher is None we should start a new one
                    if leecher == None:
                        chunk_idx = self.find_rarest_chunk()
                        #Rarest chunk == None indicates the brook is finished
                        if chunk_idx == None:
                            self.done = True
                        else:
                            peer = self._network.find_peer_with_chunk(chunk_idx)
                            #if we get None try to get another not so rare chunk
                            if peer == None:
                                chunk_idx = self.find_not_as_rare_chunk(chunk_idx)
                                peer = self._network.find_peer_with_chunk(chunk_idx)
                            if leecher_idx % 2 == 0:
                                chunk_idx = self.find_not_as_rare_chunk(chunk_idx)
                                peer = self._network.find_peer_with_chunk(chunk_idx)
                            if not peer == None and not chunk_idx in self._currently_leeching:
                                self._start_leecher(peer, chunk_idx, leechers, leecher_idx)
                            
                    elif leecher.done:
                        self._network._chunk_rarity[leecher.chunk_idx] = None
                        #Optimization1
                        self._network.update_peer_activity(leecher._conn.getpeername(), False)
                        #/Optimization1
                        self._currently_leeching.remove(leecher.chunk_idx)
                        leecher.join()
                        leechers[leecher_idx] = None
                    if leecher_idx == 11:
                        leecher_idx = 0
                    else:
                        leecher_idx += 1
                    self.done = self._brook.completed
                time.sleep(0.5)
            if not self._brook.verify_brook(self._network):
                self.done = False
            else:
                self._shutdown = True
            print "BROOK FINISHED :D"
    
    def _start_leecher(self, peer, chunk_idx, leechers, leecher_idx):
        (peer_ip, peer_port) = peer
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
        #sock = LSocket()
        sock.connect((peer_ip, peer_port))
        self._send(sock, struct.pack(COMMAND_FORMAT, "REQUEST", chunk_idx))
        response = self._recieve_ack(sock)
        if response == "ACK":
            sock.send("ACK")
            #Optimization1
            self._network.update_peer_activity(peer, True)
            #/Optimization1
            self._currently_leeching.add(chunk_idx)
            leecher = LeechWorker(sock, chunk_idx, self._brook, self._network)
            leecher.start()
            leechers[leecher_idx] = leecher
            if not self._network._chunk_rarity[chunk_idx] == None:
                self._network._chunk_rarity[chunk_idx] += 1    
    
    def find_rarest_chunk(self):
        #first I find the most rare chunk
        lowest = None
        idx = 0
        for chunk in self._network._chunk_rarity:
            if chunk == None:
                pass
            elif lowest == None:
                lowest = chunk
            elif chunk < lowest:
                lowest = chunk
            idx += 1
        #if lowest is None at this point it means that all chunks in _chunk_rarity was None i.e. all completed
        if lowest == None:
            return None
        #then I add all chunks with that rarity to a set
        tmp = set()
        idx = 0
        for chunk in self._network._chunk_rarity:
            if chunk == lowest:
                tmp.add(idx)
            idx += 1
        #and return a random
        return random.choice(list(tmp))
        
    def find_not_as_rare_chunk(self, chunk_idx):
        #if chunk_idx == None:
        #    return self.find_rarest_chunk()
        low_limit = self._network._chunk_rarity[chunk_idx]
        lowest = None
        idx = 0
        for chunk in self._network._chunk_rarity:
            if chunk == None:
                pass
            elif chunk <= low_limit:
                pass
            elif lowest == None:
                lowest = chunk
            elif chunk < lowest:
                lowest = chunk
            idx += 1
        if lowest == None:
            return chunk_idx
        #then I add all chunks with that rarity to a set
        tmp = set()
        idx = 0
        for chunk in self._network._chunk_rarity:
            if chunk == lowest:
                tmp.add(idx)
            idx += 1
        #and return a random
        return random.choice(list(tmp)) 

    def shutdown(self, block = True):
        self._shutdown = True
        if block:
            self._shutdown_signal.acquire()
            self._shutdown_signal.release()
    
    def _recieve_ack(self, sock):
        retries = 0
        while retries < 10:
            try:
                return sock.recv(1024)
            except BaseException:
                retries += 1
                if retries == 10:
                    raise BaseException
    
    def _send(self, sock, data):
        sent = 0
        while sent < len(data):
            sent += sock.send(data[sent:])